package Engine;

import Control.GameController;
import Objects.Debris;
import Objects.GenericObject;
import Objects.MovableObject;
import Ships.Ship;
import Players.ComputerPlayer;
import Players.HumanPlayer;
import com.sun.opengl.util.GLUT;
import gleem.linalg.Vec3f;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import javax.media.opengl.GLAutoDrawable;

/**
 * Classe do motor do jogo.
 *
 * @author Gustavo Buzogany Eboli
 */
public class Engine {

    private static Engine instanceOf;
    private GLUT glut;
    private CollisionManager collManager;
    private GLAutoDrawable drawableRef;
    private ArrayList<Integer> removeList;
    private TreeMap movableMap;
    private ArrayList<GenericObject> staticList;
    private TreeMap selected;
    private HumanPlayer player;
    private int objectCount;
    private QuadTree quadTree;
    private int mousex, mousey;
    private boolean clicked;
    private Renderer renderer;
    private KeyActions actionHandler;
    private int enemies = 0;
    private int discount = 0;
    private GameController gc;

    /**
     * M?todo construtor privado (singleton)
     */
    private Engine() {
        removeList = new ArrayList<Integer>();
        staticList = new ArrayList<GenericObject>();
        selected = new TreeMap();
        movableMap = new TreeMap();

        actionHandler = KeyActions.getInstance();

        Vec3f supEsq = new Vec3f(50000.0f, 50000.0f, 0.0f);
        Vec3f infDir = new Vec3f(50000.0f, 50000.0f, 0.0f);

        quadTree = new QuadTree(supEsq, infDir, 1);

        renderer = Renderer.getInstance();

        collManager = new CollisionManager(movableMap, quadTree);

        objectCount = 0;
    }


    public void setGameController(GameController gc)
    {
        this.gc = gc;
    }

    public TreeMap getSelected() {
        return this.selected;
    }

    /**
     * Pega o proximo identificador de objeto
     * @return Retorna proximo identificador de objeto
     */
    public int getNextIdentifier() {
            return objectCount++;
    }

    /**
     * M?todo gerador do "lixo" espacial.
     * @param drawable refer?ncia do GLAutoDrawable.
     */
    private void debrisGenerator(GLAutoDrawable drawable) {
        Random rand = new Random();
        Debris aux;
        for (int i = 0; i < 1000; i++) {
            aux = new Debris(getNextIdentifier(), getInstance());
            aux.setPos((float) (rand.nextInt(2500) - 1250), (float) (rand.nextInt(2500) - 1250), (float) -1 * (rand.nextInt(600)+400));
            aux.setDrawable(drawable);
            staticList.add(aux);
        }
    }

    /**
     * Remove um movable da lista de objetos moveis
     * @param id Identificador do objeto a ser removido
     */
    public void removeMovable(int id) {
        removeList.add(id);
    }

    /**
     * Processa lista de objetos a ser removidos
     */
    private void processRemoveList()
    {
        Integer aux;
        int conv;
        Iterator it = removeList.iterator();
        while(it.hasNext())
        {
            aux = (Integer) it.next();
            MovableObject maux = (MovableObject) movableMap.get(aux);
            if(maux != null)
            if(maux.getBotFlag() == true)
            {
                Random rand = new Random();
                int loot = rand.nextInt(1000);
                if(loot < 100)
                {
                    System.out.println("Loot!");
                }
                if(maux.getHealth() <= 0)
                    player.increaseExp(maux.getKillExp());
                if (enemies < 20) {
                    ComputerPlayer bot = new ComputerPlayer(Engine.getInstance(), drawableRef);

                    if (discount == 0) {
                        bot = new ComputerPlayer(Engine.getInstance(), drawableRef);
                        enemies += 1;
                    } else {
                        discount--;
                    }
                } else {
                    enemies--;
                }

            }
            movableMap.remove(aux);
        }
        removeList.clear();
    }

    /**
     * Insere novo objeto movel na lista de objetos moveis
     * @param newMovable
     */
    public void insertMovable(GenericObject newMovable) {
        movableMap.put(newMovable.getID(), newMovable);
    }

    /**
     * M?todo respons?vel por retornar a ?nica inst?ncia da classe (singleton)
     * @return Retorna a ?nica inst?ncia da classe.
     */
    public static Engine getInstance() {
        if (instanceOf == null) {
            instanceOf = new Engine();
        }
        return instanceOf;
    }

    /**
     * M?todo respons?vel pela inicializa??o do motor do jogo.
     * @param drawable refer?ncia do GLAutoDrawable.
     */
    public void init(GLAutoDrawable drawable) {

        drawableRef = drawable;
        glut = new GLUT();
        Random rand = new Random();

        Ship playerShip = new Ship(getNextIdentifier(), getInstance(), 1); // 1 = x-wing
        playerShip.setDrawable(drawable);
        playerShip.setMass(0.2f);
        playerShip.setPlayerFlag(true);
        playerShip.setCullingVisible(true);
        movableMap.put(playerShip.getID(), playerShip);

        player = new HumanPlayer();
        player.setMyShip(playerShip);
 
        ComputerPlayer bot = new ComputerPlayer(Engine.getInstance(), drawable);
        enemies++;

        actionHandler.setEngine(getInstance());

        renderer.setDrawable(drawable);
        renderer.setEngine(Engine.getInstance());
        renderer.loadModel();

        debrisGenerator(drawable);
    }

    /**
     * M?todo chamado quando o OpenGL manda atualizar a tela.
     */
    public void step() {
        actionHandler.pool();
        update();
        processRemoveList();
    }


    /**
     * M?todo respons?vel por atualizar todos os elementos do jogo.
     */
    private void update() {
        collManager.handleObjectObjectCollisions();
        MovableObject aux_m;

        Integer auxKey;
        Set key = movableMap.keySet();

        Iterator it = key.iterator();

        while (it.hasNext()) {
            auxKey = (Integer) it.next();
            aux_m = (MovableObject) movableMap.get(auxKey);
            aux_m.update(quadTree);
        }
        /*
        StaticObject aux_s;
        it = staticList.iterator();
        while (it.hasNext()) {
            aux_s = (StaticObject) it.next();
            aux_s.update(quadTree);
        }*/
    }

    /**
     * M?todo que retorna um ponteiro para a inst?ncia do jogador.
     * @return Retorna um ponteiro para a inst?ncia do jogador.
     */
    public HumanPlayer getHumanPlayer() {
        return player;
    }

    public TreeMap getMovableMap()
    {
        return this.movableMap;
    }

    public ArrayList<GenericObject> getStaticList()
    {
        return this.staticList;
    }

    public KeyActions getActionHandler() {
        return this.actionHandler;
    }

    public void removeSelected(Integer id)
    {
        selected.remove(id);
    }

    public static void killEngine()
    {
        instanceOf = null;
    }

    /**
     * Desconta inimigos que sairam do alcance do jogador. Caso contrario, seriam criado dois inimigos para cada inimigo que sai do alcance do jogador.
     */
    public void increaseDiscount() {
        discount++;
    }

    public GameController getGC()
    {
        return this.gc;
    }

    public int getEnemiesCount() {
        return this.enemies;
    }

    public void setEnemies(int enemies) {
        this.enemies = enemies;
    }

    /**
     * Gera os inimigos quando um jogo eh carregado
     */
    public void generateEnemies() {
        for(int i=0;i<enemies;i++)
        {
            ComputerPlayer bot = new ComputerPlayer(Engine.getInstance(), drawableRef);
        }
    }

    
}
